home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / Folder Watching / Folder Watcher FBA ƒ / Sources / FBATask.c < prev    next >
Encoding:
Text File  |  1996-02-16  |  14.5 KB  |  488 lines  |  [TEXT/CWIE]

  1. // FBATask.c
  2. //
  3. // Folder Watcher FBA by Greg Sutton
  4. // ©Apple Computer Inc 1996, all rights reserved.
  5.  
  6.  
  7. #include "FBATask.h"
  8.  
  9. #include "FBA.h"
  10. #include "FBAAppleEvents.h"
  11. #include "FBALists.h"
  12.  
  13. #include <Errors.h>
  14. #include <Resources.h>
  15. #include <Processes.h>
  16. #include <TextUtils.h>
  17.  
  18.  
  19. typedef struct
  20. {
  21.     long                theState;        // We check for folder changes in stages - this is the state
  22.     unsigned long        theLastTime;    // The last time a volume check was initiated.
  23.     long                theResultIndex;    // For stpping through the results of the PBCatSearch().
  24.     FSSpec*                theInfoDir;        // FSSpec for a folder from PBCatSearch() results - this
  25.                                         //  has PBGetCatInfo() called for number of files.
  26. } SearchStateRec, *SearchStatePtr;
  27.  
  28.  
  29.     // Prototypes
  30. long                    DoSearchIdle( void );
  31. long                    DoWaitForCatSearch( void );
  32. long                    DoCheckCatSearchResults( void );
  33. long                    DoSearchNextVolume( void );
  34.  
  35. static unsigned long    GetAverageSleepTime( void );
  36. static OSErr            GetTargetApplicationCreator( OSType* theCreator );
  37. static Boolean            TargetApplicationRunning( ProcessSerialNumber* thePSN );
  38. static Boolean            GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN );
  39.  
  40. static void                StartCatSearch( WatchVolumePtr theVolPtr );
  41. static void                StartCatInfo( FSSpec* theSpec );
  42. static Boolean            CheckCatInfoResult( ProcessSerialNumber* thePSN );
  43.  
  44.  
  45.     // Constants
  46. const long        MaxFiles = 200;            // Maximum number of files received from PBCatSearch()
  47. const long        BufferSize = 16 * 1024;    // Buffer size for PBCatSearch()
  48. const long        DiskAccessSleep = 5;    // 5 ticks to WaitNextEvent() after calling an
  49.                                         //    asynchronous file access call.
  50. const long        MaxBatchEvents = 20;    // Limit the Apple events we send between
  51.                                         //  WaitNextEvent() calls.            
  52.  
  53. enum        // Values for theState of our SearchStateRec
  54. {
  55.     kSearchIdle = 0, kWaitForCatSearch, kCheckCatSearchResults, kSearchNextVolume
  56. };
  57.  
  58.  
  59.     // Globals
  60. SearchStateRec    gSearchState;            // Current state of search.
  61. CSParam            gCSParamRec;            // PBCatSearch() parameter block.
  62. CInfoPBRec        gSearchInfo1;            // Add these addresses to the parameter block
  63. CInfoPBRec        gSearchInfo2;            // for defining search space.
  64. CInfoPBRec        gCInfoPBRec;            // PBGetCatInfo() parameter block.
  65.  
  66. OSType            gTargetCreator;            // The creator type of the application
  67.                                         //  to send change events to.
  68.  
  69.  
  70. // Initialise all of the memory we shall use for calls to PBCatSearch().
  71. // We also set all the PBCatSearch() parameter block fields which
  72. // will not change.
  73.  
  74. Boolean        InitTask( void )
  75. {
  76.     OSErr        err;
  77.     Boolean        result = false;
  78.     
  79.             // Make sure we have a target application.
  80.  
  81.     err = GetTargetApplicationCreator( &gTargetCreator );
  82.     if ( noErr != err ) goto done;
  83.     
  84.             // Set up our global search state
  85.  
  86.     gSearchState.theState = kSearchIdle;
  87.     GetDateTime( &gSearchState.theLastTime );
  88.     gSearchState.theResultIndex = 0;
  89.     gSearchState.theInfoDir = NULL;
  90.  
  91.             // Set up our global PBCatSearch parameter block
  92.  
  93.     gCSParamRec.ioNamePtr = NULL;
  94.  
  95.     gCSParamRec.ioSearchInfo1 = &gSearchInfo1;
  96.     gCSParamRec.ioSearchInfo2 = &gSearchInfo2;
  97.     
  98.     gCSParamRec.ioMatchPtr = (FSSpecPtr)NewPtr( sizeof(FSSpec) * MaxFiles );  
  99.     if ( ! gCSParamRec.ioMatchPtr ) goto done;
  100.     gCSParamRec.ioReqMatchCount = MaxFiles;
  101.     
  102.     gCSParamRec.ioOptBuffer = NewPtr( BufferSize );
  103.     if ( ! gCSParamRec.ioOptBuffer ) goto done;
  104.     gCSParamRec.ioOptBufSize = BufferSize;
  105.  
  106.             // Set up PBGetCatInfo() parameter block
  107.  
  108.     gCInfoPBRec.dirInfo.ioResult = 0;
  109.     
  110.     result = true;
  111.     
  112. done:
  113.     return result;
  114. }
  115.  
  116.  
  117. // This routine coordinates the different stages of the search.
  118. // It returns the number of ticks for sleep in WaitNextEvent().
  119.  
  120. long    DoBackgroundTask( void )
  121. {
  122.     long    result;
  123.  
  124.     switch ( gSearchState.theState )
  125.     {
  126.         case kSearchIdle:                            // Check it is time, and we can run
  127.             result = DoSearchIdle( );                //  off another volume search.
  128.             break;
  129.             
  130.         case kWaitForCatSearch:                        // Make sure the PBCatSearch() completed
  131.             result = DoWaitForCatSearch( );            //  with no errors.
  132.             break;
  133.             
  134.         case kCheckCatSearchResults:                // Our PBCatSearch() on the directory
  135.             result = DoCheckCatSearchResults( );    //  modification dates has completed
  136.             break;                                    //  so act on the results.
  137.             
  138.         case kSearchNextVolume:                        // Set up the task for the next volume.
  139.             result = DoSearchNextVolume( );
  140.             break;
  141.     }
  142.  
  143.     return result;        // Result is in ticks
  144. }
  145.  
  146.  
  147. // Check whether it's time to start another search.
  148. // If it is then start a PBCatSearch.
  149. // If it isn't then return an updated time for WaitNextEvent().
  150.  
  151. long    DoSearchIdle( void )
  152. {
  153.     unsigned long    now;
  154.     long            result;
  155.     
  156.     if ( ! GetHeadVolumePtr( ) )
  157.         return GetAverageSleepTime( ) * 60;    // Time to WaitnextEvent() in ticks
  158.     
  159.     GetDateTime( &now );    // Time now in seconds
  160.     
  161.                 // Calculate time in seconds then multiply to ticks
  162.     result = (( gSearchState.theLastTime + GetAverageSleepTime( ) ) - now ) * 60;
  163.     
  164.                 // Time for another PBCatSearch() check
  165.     if ( result <= 0 )
  166.     {
  167.         StartCatSearch( GetHeadVolumePtr( ) );
  168.         
  169.         result = DiskAccessSleep;    // Give over some time to other applications but
  170.     }                                // we want to check the results of the search soon.
  171.  
  172.     return result;
  173. }
  174.  
  175.  
  176. // Check that the PBCatSearch() has completed. Also check the error returned
  177. // by the PBCatSearch() and act appropriately.
  178.  
  179. long    DoWaitForCatSearch( void )
  180. {
  181.     long            result;
  182.     
  183.     if ( gCSParamRec.ioResult >= 0 )    // The PBCatSearch() still hasn't finished
  184.         return DiskAccessSleep;            //  try again in a litte while.
  185.     
  186.  
  187.     switch ( gCSParamRec.ioResult )
  188.     {
  189.         case eofErr:                    // The search completed succesfully so
  190.             gSearchState.theState = kCheckCatSearchResults;
  191.             break;                        //  we can now look at the results.
  192.             
  193.         case catChangedErr:                // The catalog search as interrupted so results
  194.         case afpCatalogChanged:            //  could be miss some changes.
  195.             gSearchState.theState = kSearchIdle;    // The idle call will kick off
  196.             break;                                    //  another PBCatSearch() on the same
  197.                                                     //  volume straight away.
  198.  
  199.         default:                        // Some kind of problem with the search
  200.                                         //  try going onto the next volume.
  201.             gSearchState.theState = kSearchNextVolume;    
  202.     }
  203.     
  204.     
  205.     result = DoBackgroundTask( );        // Act on the result straight away.
  206.  
  207.     return result;
  208. }
  209.  
  210.  
  211. // The PBCatSearch() has completed - check the results in the parameter block.
  212. // This function 
  213.  
  214. long    DoCheckCatSearchResults( void )
  215. {
  216.     ProcessSerialNumber    aPSN;
  217.     FSSpec*                anFSSpecPtr;
  218.     WatchFolderPtr        aWatchFolderPtr;
  219.     long                eventsSent = 0;
  220.     long                result = 0;
  221.     
  222.     if ( TargetApplicationRunning( &aPSN ) )
  223.     {
  224.         if ( ! CheckCatInfoResult( &aPSN ) )
  225.             return DiskAccessSleep;            // Must still be waiting for the PBGetCatInfo()
  226.                                             //  call on a watch folder that's been modified.
  227.  
  228.                     // May pick up the loop where we left off
  229.         for ( ; gSearchState.theResultIndex < gCSParamRec.ioActMatchCount; gSearchState.theResultIndex++ )
  230.         {
  231.             anFSSpecPtr = &gCSParamRec.ioMatchPtr[gSearchState.theResultIndex];
  232.             aWatchFolderPtr = FolderInList( anFSSpecPtr );
  233.         
  234.             if ( aWatchFolderPtr )
  235.             {
  236.                 StartCatInfo( anFSSpecPtr );    // Start an asychronous PBGetCatInfo() call
  237.                 gSearchState.theResultIndex++;    // Increment for next DoCheckCatSearchResults() call.
  238.                 return DiskAccessSleep;            // Return immediately
  239.             }
  240.             else if ( VolumeAndDirIDInList( anFSSpecPtr->vRefNum, anFSSpecPtr->parID ) )
  241.             {
  242.                 SendChangeEvent( &aPSN, anFSSpecPtr, kTypeFileModified );
  243.                 eventsSent++;
  244.                 if ( eventsSent >= MaxBatchEvents )
  245.                 {
  246.                     gSearchState.theResultIndex++;
  247.                     return DiskAccessSleep;    // Give the target application a bit of time
  248.                 }                            // to catch up with events sent.
  249.             }
  250.         }
  251.     }
  252.  
  253.         // If we get this far then we have finished looking at all the results.
  254.  
  255.     gSearchState.theState = kSearchNextVolume;
  256.     gSearchState.theResultIndex = 0;        // Reset the index for PBCatSearch() results.
  257.  
  258.     result = DoBackgroundTask( );            // Get a time via kSearchNextVolume
  259.                                             // which goes onto kSearchIdle.
  260.  
  261.     return result;
  262. }
  263.  
  264.  
  265. // Update the time searched up to on the current volume.
  266. // Set the next volume to search as the next in the volume list.
  267. // Set the task back to kSearchIdle.
  268.  
  269. long    DoSearchNextVolume( void )
  270. {
  271.     WatchVolumePtr    tempPtr = GetHeadVolumePtr( );
  272.     unsigned long    result = 0;
  273.  
  274.     if ( tempPtr )        // Update the date checked up to
  275.         tempPtr->theLastModCheck = gSearchInfo2.dirInfo.ioDrMdDat;
  276.  
  277.     FirstVolumeToLast( );                    // Cycle around volumes containing watched folders
  278.     gSearchState.theState = kSearchIdle;    // Back to start
  279.     
  280.     result = DoBackgroundTask( );            // Get a time via kSearchIdle
  281.  
  282.     return result;
  283. }
  284.  
  285.  
  286. // Check that we're not in an asychronous call so that we can quit.
  287. // If we were in an asychronous call then the memory we've allocated for
  288. // the results will be disposed of if we quit. This may result in writing
  289. // over someone elses memory.
  290.  
  291. Boolean    CanQuitTask( void )
  292. {
  293.     Boolean        result;
  294.  
  295.     switch ( gSearchState.theState )
  296.     {
  297.         case kWaitForCatSearch:            // Check that search is complete
  298.             result = ( gCSParamRec.ioResult < 0 );
  299.             break;
  300.             
  301.         case kCheckCatSearchResults:    // Check we're not waiting for a PBGetCatInfo()
  302.             result = ( gCInfoPBRec.dirInfo.ioResult <= 0 );
  303.             break;
  304.     
  305.         default:
  306.             result = true;
  307.     }
  308.     
  309.     return result;
  310. }
  311.  
  312.  
  313. // Each cycle must check every volume with watch folders on it. Therefore
  314. // we need to divide up the cycle time by the number of volumes.
  315. // Time returned is in seconds.
  316.  
  317. static unsigned long    GetAverageSleepTime( void )
  318. {
  319.     unsigned long    result = 0;
  320.  
  321.     if ( GetNumberOfVolumes( ) )
  322.     {
  323.         result = GetCycleTime( ) / GetNumberOfVolumes( );
  324.         if ( ! result )                // If this happens we've got alot of volumes to check!
  325.             result = 1;                // Don't want to return a zero.
  326.     }
  327.     else
  328.         result = GetCycleTime( );    // Someone may add a folder - so check every cycle time
  329.     
  330.     return result;
  331. }
  332.  
  333.  
  334. // The creator type of the target application is stored in a 'Targ' resource.
  335. // This routine just grabs the first resource of this type if there is one.
  336.  
  337. static OSErr    GetTargetApplicationCreator( OSType* theCreator )
  338. {
  339.     short            count;
  340.     Handle             aHandle;
  341.     OSErr            result = resNotFound;
  342.     
  343.     count = Count1Resources( kTargetAppResource );
  344.     if ( count )
  345.     {
  346.         aHandle = Get1IndResource( kTargetAppResource, 1 );
  347.         if ( aHandle )
  348.         {
  349.             *theCreator = *(OSType *) *aHandle;
  350.             ReleaseResource( aHandle );
  351.             result = noErr;
  352.         }
  353.     }
  354.     
  355.     return result;
  356. }
  357.  
  358.  
  359. // Check that the application is running. If a pointer to a ProcessSerialNumber
  360. // is given it will be filled in if the routine reutrns true.
  361.  
  362. static Boolean    TargetApplicationRunning( ProcessSerialNumber* thePSN )
  363. {
  364.     ProcessSerialNumber    aPSN;        // Use this if no ProcessSerialNumber supplied
  365.     Boolean                result;
  366.     
  367.     if ( thePSN )
  368.         result = GetCreatorApplicationPSN( gTargetCreator, thePSN );
  369.     else
  370.         result = GetCreatorApplicationPSN( gTargetCreator, &aPSN );
  371.     
  372.     return result;
  373. }
  374.  
  375.  
  376. static Boolean    GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN )
  377. {
  378.     ProcessInfoRec        aProcessInfoRec;
  379.     FSSpec                 processFSSpec;
  380.     Boolean                result = false;
  381.  
  382.         // check the current processes to see if the application is already
  383.         // running, and get its process serial number.
  384.     thePSN->lowLongOfPSN = kNoProcess;
  385.     thePSN->highLongOfPSN = 0;
  386.     
  387.     aProcessInfoRec.processInfoLength = sizeof( FSSpec );
  388.     aProcessInfoRec.processName = NULL;
  389.     aProcessInfoRec.processAppSpec = &processFSSpec;
  390.     
  391.     while ( noErr == GetNextProcess( thePSN ) )
  392.         if ( noErr == GetProcessInformation( thePSN, &aProcessInfoRec ) )
  393.             if ( aProcessInfoRec.processSignature == theCreator )
  394.             {
  395.                 result = true;
  396.                 break;
  397.             }
  398.  
  399.     return result;
  400. }
  401.  
  402.  
  403. // This routine sets off a PBCatSearch() on the given volume to see if any of the
  404. // directories or files have been modified. Directory changes occur when a file or folder is
  405. // added or removed.
  406.  
  407. static void    StartCatSearch( WatchVolumePtr theVolPtr )
  408. {
  409.     GetDateTime( &gSearchState.theLastTime );        // Keep for cycle time updates even
  410.                                                     //  if target isn't running
  411.  
  412.     if ( TargetApplicationRunning( NULL ) )
  413.     {
  414.             // Set up the variable parts of the PBCatSearch() parameter block.
  415.             // Note that some has been set up in InitTask().
  416.  
  417.         gCSParamRec.ioCompletion = NULL;                // Asychronous call with no completion routine
  418.         gCSParamRec.ioVRefNum = theVolPtr->theVolRef;
  419.         
  420.         gSearchInfo1.dirInfo.ioDrMdDat = theVolPtr->theLastModCheck + 1;
  421.         GetDateTime( &gSearchInfo2.dirInfo.ioDrMdDat );    // Up to now
  422.     
  423.             // Searching on the modification date of directories and files
  424.         gCSParamRec.ioSearchBits = fsSBFlMdDat;
  425.         
  426.         gCSParamRec.ioSearchTime = 0;                    // Allow as much time as needed
  427.         gCSParamRec.ioCatPosition.initialize = 0;
  428.         
  429.             // Update the global search state before we call the asynchronous routine
  430.         
  431.         gSearchState.theState = kWaitForCatSearch;         // Where to go next.
  432.                                         
  433.         (void)PBCatSearch( &gCSParamRec, true );        // Do the modification search
  434.     }
  435.     else    // Don't bother accessing the disk - just act like we have done the search
  436.         gSearchState.theState = kSearchNextVolume;
  437. }
  438.  
  439.  
  440. static void    StartCatInfo( FSSpec* theSpec )
  441. {
  442.     gSearchState.theInfoDir = theSpec;        // Set so we know what directory information
  443.                                             //  we're waiting for.
  444.     gCInfoPBRec.dirInfo.ioCompletion = NULL;// Asychronous call with no completion routine
  445.     gCInfoPBRec.dirInfo.ioNamePtr = theSpec->name;
  446.     gCInfoPBRec.dirInfo.ioVRefNum = theSpec->vRefNum;
  447.     gCInfoPBRec.dirInfo.ioDrDirID = theSpec->parID;
  448.     gCInfoPBRec.dirInfo.ioFDirIndex = 0;    // Use ioNamePtr and ioDirID
  449.     gCInfoPBRec.dirInfo.ioACUser = 0;        // If this does not compile try using
  450.                                             // thePB->dirInfo.filler2 or thePB->dirInfo.ioACUser
  451.                                             // Clear it before calling PBGetCatInfo()
  452.     (void)PBGetCatInfo( &gCInfoPBRec, true );
  453. }
  454.  
  455.  
  456. // Check that a PBGetCatInfo() was beng waited for. if it was and it's completed
  457. // then send off change event to target application.
  458. // This routine will return false if the result of the PBGetCatInfo() is still being waited for.
  459.  
  460. static    Boolean    CheckCatInfoResult( ProcessSerialNumber* thePSN )
  461. {
  462.     WatchFolderPtr        aWatchFolderPtr;
  463.     
  464.     if ( ! gSearchState.theInfoDir )            // No PBGetCatInfo() being waited for.
  465.         return true;
  466.     
  467.     if ( gCInfoPBRec.dirInfo.ioResult > 0 )        // Still waiting for PBGetCatInfo()
  468.         return false;
  469.     
  470.     aWatchFolderPtr = FolderInList( gSearchState.theInfoDir );
  471.  
  472.     if ( aWatchFolderPtr && noErr == gCInfoPBRec.dirInfo.ioResult )
  473.     {
  474.         if ( aWatchFolderPtr->theFileCount < gCInfoPBRec.dirInfo.ioDrNmFls )
  475.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileAdded );
  476.         else if ( aWatchFolderPtr->theFileCount > gCInfoPBRec.dirInfo.ioDrNmFls )
  477.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileRemoved );
  478.         else    // Changed but we don't know whether modified or not
  479.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileModified );
  480.         
  481.         aWatchFolderPtr->theFileCount = gCInfoPBRec.dirInfo.ioDrNmFls;
  482.     }
  483.     
  484.     gSearchState.theInfoDir = NULL;    // This is set to tell us we are waiting for the PBGetCatInfo()
  485.  
  486.     return true;
  487. }
  488.